<template>
  <div style="min-height: 768px" class="h-screen w-full bg-gray-200">
    <el-page-header class="header h-14 px-4 bg-white flex items-center border-b-2" @back="onClickBackButton"><template
        #title>
        <div style="min-width: 11rem" class="w-44 text-sm text-center truncate">
          {{ project.name }}
        </div>
      </template>
      <template slot="content">
        <div class="flex items-center space-x-2">
          <div v-for="i in headerTools" :key="i.name" :class="{ active: toolActive === i.name }" class="
              flex flex-col
              justify-around
              items-center
              w-12
              h-12
              rounded
              cursor-pointer
              hover:bg-purple-100
            " @click="selectTool(i.name)">
            <img class="w-6 h-6" :src="i.icon" />
            <p class="text-xs">{{ i.name }}</p>
          </div>
        </div>
        <div>
          <el-button type="primary" @click="exportProject">
            导出项目
          </el-button>
          <el-button type="primary" @click="$refs.generalOptionsDialog.open()">
            生成PDF文件
          </el-button>
        </div>
      </template>
    </el-page-header>
    <div style="height: calc(100% - 3.5rem)" class="relative w-full">
      <div class="absolute top-0 left-0 w-72 h-full">
        <el-tabs class="bg-white h-1/4">
          <el-tab-pane label="元件" class="p-2 grid grid-cols-2 grid-rows-1 justify-items-center">
            <div v-for="i in elementTools" :key="i.name" :class="{ active: toolActive === i.name }" class="
                flex flex-col
                justify-around
                items-center
                w-24
                h-24
                rounded-md
                cursor-pointer
                hover:bg-purple-100
              " @click="selectTool(i.name)">
              <img class="w-12 h-12" :src="i.icon" />
              <p class="text-sm">{{ i.name }}</p>
            </div>
          </el-tab-pane>
        </el-tabs>
        <el-tabs class="bg-white h-3/4">
          <el-tab-pane class="h-full" label="页面">
            <el-collapse class="h-full" value="1">
              <el-collapse-item name="1">
                <template slot="title">
                  <div class="flex items-center pl-4">
                    <img class="w-8 h-8" :src="require('@/assets/icon/pdf_file.svg')" />
                    <span class="ml-2">{{ project.pdf.name }}</span>
                  </div>
                </template>
                <div class="
                    flex
                    items-center
                    pl-8
                    w-full
                    h-10
                    cursor-pointer
                    hover:bg-indigo-100
                  " v-for="i in PDFPageList" :key="i" :class="{ active: project.currentPage === i }"
                  @click="project.currentPage = i">
                  <img class="w-8 h-8" :src="require('@/assets/icon/pdf_page.svg')" />
                  <span class="ml-2">第{{ i }}页</span>
                </div>
              </el-collapse-item>
            </el-collapse>
          </el-tab-pane>
          <el-tab-pane label="概要">
            <div class="
                flex
                items-center
                pl-8
                w-full
                h-10
                cursor-pointer
                hover:bg-indigo-100
              " v-for="i in currentPageElements" :class="{ active: selectedNode === i.element }" :key="i.name"
              @click="setSelectedNode(i)">
              <img class="w-8 h-8" :src="i.image" />
              <span class="ml-2 text-sm">{{ i.name }} </span>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div>
      <el-tabs v-if="selectedNode" class="absolute top-0 right-0 bg-white w-72 h-full">
        <el-tab-pane class="h-full" label="属性">
          <el-collapse class="p-2 h-full" :value="['1', '2', '3', '4', '5', '6', '7', '99', '100']">
            <el-collapse-item title="位置和尺寸" name="1">
              <div class="grid grid-cols-2 grid-rows-2 justify-items-center gap-2">
                <el-input :value="elementInfo.x">
                  <template slot="prepend">X</template>
                </el-input>
                <el-input :value="elementInfo.y">
                  <template slot="prepend">Y</template>
                </el-input>
                <el-input :value="elementInfo.width">
                  <template slot="prepend">W</template>
                </el-input>
                <el-input :value="elementInfo.height">
                  <template slot="prepend">H</template>
                </el-input>
              </div>
            </el-collapse-item>
            <el-collapse-item title="码点类型" name="2">
              <el-select style="width: 100%" v-model="elementInfo.codePointType" placeholder="请选择码点类型"
                @change="setCodeType">
                <el-option label="手写码" :value="0"></el-option>
                <el-option label="点读码" :value="1"></el-option>
              </el-select>
            </el-collapse-item>
            <el-collapse-item title="铺码形状" name="3">
              <el-select style="width: 100%" v-model="elementInfo.shape" placeholder="请选择铺码形状">
                <el-option label="矩形" :value="0"></el-option>
                <!-- <el-option label="圆形" :value="1"></el-option> -->
              </el-select>
            </el-collapse-item>
            <el-collapse-item v-if="elementInfo.codePointType === 0" title="码点信息" name="4">
              <div class="grid grid-cols-2 grid-rows-2 justify-items-center gap-2">
                <el-input :value="project.writecode.sid">
                  <template slot="prepend">Section</template>
                </el-input>
                <el-input :value="project.writecode.oid">
                  <template slot="prepend">Owner</template>
                </el-input>
                <el-input :value="project.writecode.bid">
                  <template slot="prepend">Book</template>
                </el-input>
                <el-input :value="project.currentPage">
                  <template slot="prepend">Page</template>
                </el-input>
              </div>
            </el-collapse-item>
            <el-collapse-item v-if="elementInfo.codePointType === 1" title="码点信息" name="4">
              <div class="grid grid-cols-2 grid-rows-2 justify-items-center gap-2">
                <el-input :value="project.readcode.sa">
                  <template slot="prepend">SA</template>
                </el-input>
                <el-input :value="project.readcode.sb">
                  <template slot="prepend">SB</template>
                </el-input>
                <el-input :value="project.readcode.sc">
                  <template slot="prepend">SC</template>
                </el-input>
                <el-input :value="project.readcode.sd">
                  <template slot="prepend">SD</template>
                </el-input>
              </div>
            </el-collapse-item>
            <el-collapse-item v-if="elementInfo.codePointType === 1" title="点读码index" name="99">
              <div class="flex">
                <el-input class="mr-2" :value="elementInfo.index"></el-input>
                <el-button type="primary" @click="setReadCodeIndex">
                  修改index
                </el-button>
              </div>
            </el-collapse-item>
            <el-collapse-item v-if="elementInfo.codePointType === 1" title="点读码原始值" name="100">
              <div class="text-center">{{ readCodeRealValue }}</div>
            </el-collapse-item>
            <el-collapse-item title="12*12码点位置和尺寸" name="5">
              <div class="grid grid-cols-2 grid-rows-1 justify-items-center gap-2">
                <el-input :value="codePointSize(elementInfo.x, 12)">
                  <template slot="prepend">X</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.y, 12)">
                  <template slot="prepend">Y</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.width, 12)">
                  <template slot="prepend">W</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.height, 12)">
                  <template slot="prepend">H</template>
                </el-input>
              </div>
            </el-collapse-item>
            <el-collapse-item title="16*16码点位置和尺寸" name="6">
              <div class="grid grid-cols-2 grid-rows-1 justify-items-center gap-2">
                <el-input :value="codePointSize(elementInfo.x, 16)">
                  <template slot="prepend">X</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.y, 16)">
                  <template slot="prepend">Y</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.width, 16)">
                  <template slot="prepend">W</template>
                </el-input>
                <el-input :value="codePointSize(elementInfo.height, 16)">
                  <template slot="prepend">H</template>
                </el-input>
              </div>
            </el-collapse-item>
            <el-collapse-item title="铺码层级" name="7">
              <div class="flex">
                <el-input class="mr-2" :value="elementInfo.level"></el-input>
                <el-button type="primary" @click="setCodeLevel">
                  修改层级
                </el-button>
              </div>
            </el-collapse-item>
          </el-collapse>
        </el-tab-pane>
      </el-tabs>
      <!-- konva -->
      <div style="margin: 0 auto" class="relative rounded shadow-md" :style="konvaStyle">
        <canvas class="absolute top-0 left-0" id="background" :width="width" :height="height"></canvas>
        <div id="konva" :style="konvaStyle"></div>
      </div>
    </div>
    <general-options-dialog ref="generalOptionsDialog"></general-options-dialog>
    <upload-key-file-dialog ref="uploadKeyFileDialog" :getStoreValue="getStoreValue" :putStoreValue="putStoreValue"
      @getCodeKey="(key, value) => (this.project[key] = value)"></upload-key-file-dialog>
    <project-info-dialog ref="projectInfoDialog" :project="project"></project-info-dialog>
  </div>
</template>

<style lang="scss" scoped>
.active {
  @apply bg-indigo-100 text-indigo-600;
}

::v-deep .el-tabs {
  @apply flex flex-col;

  .el-tabs__header {
    @apply m-0;

    .el-tabs__nav-scroll {
      @apply flex justify-center;

      .el-tabs__item {
        @apply w-24 p-0 text-center;
      }
    }
  }

  .el-tabs__content {
    @apply flex-1 overflow-y-auto;
  }
}

::v-deep .el-page-header__content {
  @apply w-full h-full flex justify-between items-center;
}
</style>

<script>
// 防抖时间
const timeout = 500;
import Konva from "konva";
import * as constants from "@/common/constants";
import {
  loading,
  getPDFData,
  toFixed,
  pxToCodeSize,
  readCodeRealValue,
  downloadFile,
  jsonToBlobUrl,
} from "@/common/utils";
import {
  getStoreValue,
  getStoreExist,
  putStoreValue,
  getStoreAllKey,
} from "@/common/indexedDB";
import UploadKeyFileDialog from "@/components/UploadKeyFileDialog.vue";
import GeneralOptionsDialog from "./components/GeneralOptionsDialog.vue";
import ProjectInfoDialog from "./components/ProjectInfoDialog.vue";
export default {
  components: { GeneralOptionsDialog, UploadKeyFileDialog, ProjectInfoDialog },
  data() {
    return {
      // 宽高
      width: 0,
      height: 0,
      stage: null,
      background: null,
      backgroundContext: null,
      // 背景层
      backgroundLayer: null,
      // 框选元素层
      elementLayer: null,
      // 模式列表
      // 0-选择模式/1-创建模式
      mode: 0,
      // 工具信息
      toolActive: constants.SINGLE_SELECT_EMELENT,
      headerTools: [
        {
          name: constants.PROJECT_INFO,
          icon: require("@/assets/icon/info.svg"),
        },
        {
          name: constants.SINGLE_SELECT_EMELENT,
          icon: require("@/assets/icon/choose_ele.svg"),
        },
        {
          name: constants.DELETE_EMELENT,
          icon: require("@/assets/icon/delete_ele.svg"),
        },
      ],
      elementTools: [
        {
          name: constants.RECT_WRITE_CODE,
          icon: require("@/assets/icon/rect_write.svg"),
        },
        {
          name: constants.RECT_READ_CODE,
          icon: require("@/assets/icon/rect_read.svg"),
        },
      ],
      // 当前页元素列表
      currentPageElements: null,
      // 当前选中元素
      selectedNode: null,
      // 项目信息
      project: {
        // 缩放比例
        scale: 1,
        // 项目名称
        name: "",
        pdf: {
          name: "",
          url: "",
        },
        // 当前页
        currentPage: 0,
        // 手写码
        writecode: {
          sid: 0,
          bid: 0,
          oid: 0,
        },
        // 点读码
        readcode: {
          sa: 0,
          sb: 0,
          sc: 0,
          sd: 0,
        },
      },
      PDFObject: null, // PDF实例
      PDFPageList: [], // PDF页码数组
      PDFMaxPageNumber: 0, //PDF最大页数
      // 元素信息
      elementInfo: {
        visible: true,
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        codePointType: 0,
        shape: 0,
        index: 0,
        sa: 0,
        sb: 0,
        sc: 0,
        sd: 0,
        level: 0,
      },
      // 防抖定时器
      timer: null,
    };
  },
  computed: {
    // 项目/仓库名称
    projectName() {
      return this.$route.params.name;
    },
    konvaStyle({ width, height }) {
      return `width:${width}px;height:${height}px;`;
    },
    // 码点尺寸
    codePointSize() {
      return (px, codePointType) => {
        if (codePointType === 12) {
          return pxToCodeSize(px, 1.524)
        }
        if (codePointType === 16) {
          return pxToCodeSize(px, 2.031)
        }
      }
    },
    // 填充背景色
    // 0-手写码 1-点读码
    shapeFillColor() {
      return (type) => ["#6A71FF50", "#58CBF150"][type];
    },
    // 点读码原始值
    readCodeRealValue({ project: { readcode }, elementInfo }) {
      readcode.index = elementInfo.index;
      return readCodeRealValue(readcode);
    },
  },
  watch: {
    // 监听模式变化
    mode(newVal) {
      // 切换模式
      this.toggleMode(newVal);
    },
    async "project.currentPage"(newVal) {
      // 更新数据库currentPage
      await this.putCurrentPage(newVal);
      // PDF切换到当前页
      this.togglePDFPage();
      // 获取当前页元素列表
      await this.getCurrentPageElements();
      // 切换模式
      this.toggleMode(this.mode);
    },
  },
  methods: {
    // 导出项目
    async exportProject() {
      // 获取所有键
      const getAllKeyResult = await getStoreAllKey(this.projectName);
      if (getAllKeyResult.code !== 200) return;
      const keys = getAllKeyResult.data;
      // 获取所有键的值
      const getAllValueResult = await Promise.all(
        keys.map((i) => this.getStoreValue(i))
      );
      // 项目信息JSON
      const json = JSON.stringify(
        Object.fromEntries(
          getAllValueResult.map((i, index) => [keys[index], i.data])
        )
      );
      // 写入文件用户下载
      downloadFile(
        `${this.projectName}.${constants.PROJECT_SUFFIX}`,
        jsonToBlobUrl(json)
      );
    },
    // 设置选中元素
    setSelectedNode(item) {
      // 单选模式
      this.selectTool(constants.SINGLE_SELECT_EMELENT);
      this.selectedNode = item.element;
      this.destroyTransformer();
      this.createTransformer(this.selectedNode);
      this.elementLayer.draw();
    },
    // 设置码点类型
    setCodeType(value) {
      this.selectedNode.attrs._type = value;
      this.selectedNode.attrs._index = 0;
      this.selectedNode.attrs.fill = this.shapeFillColor(value);
      this.createTransformer(this.selectedNode);
      this.elementLayer.draw();
      this.setElementInfo(this.selectedNode);
      this.putCurrentPageElements();
    },
    // 设置铺码层级
    setCodeLevel() {
      this.triggerPopup("修改层级", {
        confirmButtonText: "修改",
        inputValue: this.selectedNode.attrs._level,
        submit: (value) => {
          const _level = Number(value);
          // 修改的level是否存在
          const exists = this.getLayerElements().find(
            (i) => i.attrs._level === _level
          );
          if (exists) {
            // $排序
            const elements = this.getLayerElements().sort(
              (a, b) => a.attrs._level - b.attrs._level
            );
            // $当前level下标
            const currentLevelIndex = elements.findIndex(
              (i) => i.attrs._level === this.selectedNode.attrs._level
            );
            // $修改的level下标
            const nextLevelIndex = elements.findIndex(
              (i) => i.attrs._level === _level
            );
            if (this.selectedNode.attrs._level > _level) {
              elements.slice(nextLevelIndex).forEach((i) => i.attrs._level++);
              elements[currentLevelIndex].attrs._level = _level;
            } else {
              elements[nextLevelIndex].attrs._level =
                elements[currentLevelIndex].attrs._level;
              elements[currentLevelIndex].attrs._level = _level;
            }
          } else {
            this.selectedNode.attrs._level = _level;
          }
          this.setElementInfo(this.selectedNode);
          this.putCurrentPageElements();
        },
      });
    },
    // 设置点读码值
    setReadCodeIndex() {
      this.triggerPopup("设置index", {
        confirmButtonText: "设置",
        inputValue: this.selectedNode.attrs._index,
        inputValidator: (value) => {
          if (value === "") {
            return "请输入index值";
          }
          if (!new RegExp(/^[0-9]*$/).test(value)) {
            return "请输入数字";
          }
          if (
            value > this.project.readcode.maxIndex ||
            value < this.project.readcode.minIndex
          ) {
            return `index在${this.project.readcode.minIndex}-${this.project.readcode.maxIndex}之间`;
          }
        },
        submit: (value) => {
          this.selectedNode.attrs._index = Number(value);
          this.setElementInfo(this.selectedNode);
          this.putCurrentPageElements();
        },
      });
    },
    // 更新元素信息
    setElementInfo(target) {
      const { _level, _index, _shape, _type, x, y, width, height } =
        target.attrs;
      this.elementInfo.codePointType = _type;
      this.elementInfo.shape = _shape;
      this.elementInfo.x = toFixed(x);
      this.elementInfo.y = toFixed(y);
      this.elementInfo.width = toFixed(width);
      this.elementInfo.height = toFixed(height);
      this.elementInfo.index = _index;
      this.elementInfo.level = _level;
    },
    // 触发弹窗
    triggerPopup(
      title,
      {
        inputValue = "",
        confirmButtonText = "确定",
        inputValidator,
        submit,
        inputPlaceholder = "",
        cancel = () => false,
      }
    ) {
      this.$prompt("", title, {
        inputValue,
        showClose: false,
        closeOnClickModal: false,
        closeOnPressEscape: false,
        confirmButtonText,
        cancelButtonText: "取消",
        inputValidator,
        inputPlaceholder,
      })
        .then(({ value }) => submit(value))
        .catch(cancel);
    },
    // 点击返回按钮
    onClickBackButton() {
      this.$router.push("/");
    },
    // 模式初始化
    initMode() {
      this.stage.off("mousedown");
      this.stage.off("click");
      // 取消鼠标移入样式
      this.contorlElementsCursor(false);
      this.selectedNode = null;
    },
    // 选择模式
    selectMode() {
      // 激活鼠标移入样式
      this.contorlElementsCursor(true);
      // 激活所有元素拖拽
      this.controlElementsDraggable(true);
      // 激活所有元素缩放
      this.stage.on("click", (e) => {
        // 点到空白区域 移除所有Transformer
        if (e.target === this.stage) {
          this.destroyTransformer();
          this.selectedNode = null;
          this.elementLayer.draw();
          return;
        }
        // 点到矩形区域 添加
        if (["rect", "circle"].map((i) => e.target.hasName(i)).some((i) => i)) {
          // 移除所有 transformer
          this.destroyTransformer();
          // 设置当前选中元素
          this.selectedNode = e.target;
          // 创建新的 transformer
          this.createTransformer(e.target);
          this.elementLayer.draw();
          return;
        }
      });
    },
    // 创建模式
    createMode() {
      // 禁用所有元素拖拽
      this.controlElementsDraggable(false);
      // 禁用所有 transformer
      this.destroyTransformer();
      // 重绘
      this.elementLayer.draw();
      // 激活监听 stage mousedown
      this.stage.on("mousedown", (downEvent) => {
        // 创建元素
        const x = downEvent.evt.offsetX;
        const y = downEvent.evt.offsetY;
        const element = this.createElement();
        element.x(x);
        element.y(y);
        // 鼠标松开
        const end = () => {
          this.stage.off("mousemove");
          this.stage.off("mouseup");
          // 宽高小于0 重新计算 坐标宽高
          if (element.width() < 0) {
            element.x(element.x() + element.width());
            element.width(element.width() * -1);
          }
          if (element.height() < 0) {
            element.y(element.y() + element.height());
            element.height(element.height() * -1);
          }
          if (element.width() < 20 || element.height() < 20) {
            // 销毁元素
            element.destroy();
            return;
          }
          // 点读码 填写index
          if ([constants.RECT_READ_CODE].includes(this.toolActive)) {
            // 填写index
            this.triggerPopup("设置index", {
              confirmButtonText: "创建",
              inputValidator: (value) => {
                if (value === "") {
                  return "请输入index值";
                }
                if (!new RegExp(/^[0-9]*$/).test(value)) {
                  return "请输入数字";
                }
                if (
                  value > this.project.readcode.maxIndex ||
                  value < this.project.readcode.minIndex
                ) {
                  return `index在${this.project.readcode.minIndex}-${this.project.readcode.maxIndex}之间`;
                }
              },
              inputPlaceholder: `index在${this.project.readcode.minIndex}-${this.project.readcode.maxIndex}之间`,
              submit: (value) => {
                element.attrs._index = Number(value);
                // update
                this.putCurrentPageElements();
                //
                this.setElementInfo(element);
                this.elementLayer.draw();
              },
              cancel: () => {
                element.destroy();
                this.elementLayer.draw();
              },
            });
            return;
          }
          // update
          this.putCurrentPageElements();
          //
          this.setElementInfo(element);
          this.elementLayer.draw();
        };
        this.stage.on("mousemove", (moveEvent) => {
          const width = moveEvent.evt.offsetX - x;
          const height = moveEvent.evt.offsetY - y;
          // 超出画布大小 终止
          if (
            x + width >= this.stage.width() ||
            y + height >= this.stage.height() ||
            x + width <= 0 ||
            y + height <= 0
          ) {
            end();
            return;
          }
          element.width(width);
          element.height(height);
          this.elementLayer.draw();
        });
        this.stage.on("mouseup", end);
      });
    },
    // 切换模式
    // 0：选择模式
    // 1：创建模式
    toggleMode(mode) {
      console.log("mode change", mode);
      // 模式初始化
      this.initMode();
      switch (mode) {
        case 0: // 选择模式
          this.selectMode();
          break;
        case 1: // 创建模式
          this.createMode();
          break;
        default:
          this.selectMode();
          break;
      }
    },
    // 选择控件
    selectTool(name) {
      // 设置区域选中值
      switch (name) {
        case constants.PROJECT_INFO: {
          // 项目信息
          this.$refs.projectInfoDialog.open();
          break;
        }
        case constants.SINGLE_SELECT_EMELENT: {
          // 模式切换
          this.mode = 0;
          this.toolActive = name;
          break;
        }
        case constants.DELETE_EMELENT: {
          // 删除元素
          this.destroyElement();
          break;
        }
        case constants.RECT_WRITE_CODE: {
          // $判断手写码是否存在
          if (!this.project.writecode) {
            this.$confirm("未检测到手写码KEY，是否现在上传？", "提示", {
              confirmButtonText: "确定",
              cancelButtonText: "取消",
              type: "warning",
            })
              .then(() => this.$refs.uploadKeyFileDialog.open(0))
              .catch(() => false);
            return;
          }
          this.mode = 1;
          this.toolActive = name;
          break;
        }
        case constants.RECT_READ_CODE: {
          // $判断点读码是否存在
          if (!this.project.readcode) {
            this.$confirm("未检测到点读码KEY，是否现在上传？", "提示", {
              confirmButtonText: "确定",
              cancelButtonText: "取消",
              type: "warning",
            })
              .then(() => this.$refs.uploadKeyFileDialog.open(1))
              .catch(() => false);
            return;
          }
          this.mode = 1;
          this.toolActive = name;
          break;
        }
        default: {
          break;
        }
      }
    },
    // 创建 Rect
    createRect({
      _type = 0,
      _level = 0,
      _index = 0,
      x = 0,
      y = 0,
      width = 0,
      height = 0,
    }) {
      const config = {
        name: "rect",
        x,
        y,
        width,
        height,
        fill: this.shapeFillColor(_type),
        _type, //0-手写码 1-点读码
        _shape: 0, //0-矩形 1-圆形
        _level, //层级
        ...(_type === 1 ? { _index } : null), //点读码index
      };
      const rect = new Konva.Rect(config);
      // 监听点击事件
      // 实时更新属性框
      rect.on("dragmove", (e) => {
        const target = e.target;
        const parent = target.parent;
        const width = ~~(target.width() * target.scaleX());
        const height = ~~(target.height() * target.scaleY());
        // 边界判断
        switch (e.type) {
          case "dragmove": {
            if (target.x() < 0) {
              target.x(0);
            }
            if (target.y() < 0) {
              target.y(0);
            }
            if (target.x() + width > parent.width()) {
              target.x(parent.width() - width);
            }
            if (target.y() + height > parent.height()) {
              target.y(parent.height() - height);
            }
            // 防抖 更新数据库
            clearTimeout(this.timer);
            this.timer = setTimeout(this.putCurrentPageElements, timeout);
            // 更新元素信息
            this.setElementInfo(target);
            break;
          }
        }
      });
      this.elementLayer.add(rect);
      return rect;
    },
    // 创建元素
    createElement() {
      const _level = this.currentPageElements
        ? this.currentPageElements.length
        : 0;
      switch (this.toolActive) {
        case constants.RECT_WRITE_CODE:
          return this.createRect({ _type: 0, _level });
        case constants.RECT_READ_CODE:
          return this.createRect({ _type: 1, _level });
      }
    },
    // 移除元素 当前元素
    destroyElement() {
      if (!this.selectedNode) return;
      // 移除所有 transformer
      this.destroyTransformer();
      // 移除元素
      this.selectedNode.destroy();
      // 同步元素列表
      this.putCurrentPageElements();
      // 更新画布
      this.elementLayer.draw();
    },
    // 创建transformer
    createTransformer(target) {
      // 边框颜色 锚点颜色
      const { _type } = target.attrs;
      const color = ["#6A71FF", "#58CBF1"][_type];
      const transformer = new Konva.Transformer({
        anchorStroke: color,
        anchorFill: color,
        anchorSize: 5,
        borderStroke: color,
        rotateEnabled: false,
        // 边界值判断
        boundBoxFunc: (oldBoundBox, newBoundBox) => {
          let box = null;
          // 判断列表
          const judges = [
            newBoundBox.x < 0,
            newBoundBox.y < 0,
            newBoundBox.x + newBoundBox.width > this.stage.width(),
            newBoundBox.y + newBoundBox.height > this.stage.height(),
            newBoundBox.height <= 20,
            newBoundBox.width <= 20,
          ];
          if (judges.some((i) => i)) {
            box = oldBoundBox;
          } else {
            box = newBoundBox;
          }
          // 更新目标属性
          target.setAttrs({
            x: box.x,
            y: box.y,
            width: box.width,
            height: box.height,
            scaleX: 1,
            scaleY: 1,
          });
          // 防抖 更新数据库
          clearTimeout(this.timer);
          this.timer = setTimeout(this.putCurrentPageElements, timeout);
          // 更新元素信息
          this.setElementInfo(target);
          return box;
        },
      });
      // 更新元素信息
      this.setElementInfo(target);
      this.elementLayer.add(transformer);
      transformer.nodes([target]);
    },
    // 移除所有transformer
    destroyTransformer() {
      this.elementLayer.find("Transformer").map((i) => i.destroy());
    },
    // 初始化 stage elementLayer
    initStage() {
      // 初始化 stage
      this.stage = new Konva.Stage({
        container: "konva",
        width: this.width,
        height: this.height,
      });
      // 初始化 - 背景层
      // this.backgroundLayer = new Konva.Layer();
      // this.stage.add(this.backgroundLayer);
      // 初始化 - 框选元素层
      this.elementLayer = new Konva.Layer();
      this.stage.add(this.elementLayer);
    },
    // 控制所有元素拖拽
    controlElementsDraggable(bool) {
      this.getLayerElements().forEach((i) => i.draggable(bool));
    },
    // 控制元素移入鼠标样式
    contorlElementsCursor(bool) {
      if (bool) {
        this.getLayerElements().forEach((i) => {
          i.on("mouseenter", () => {
            this.stage.container().style.cursor = "move";
          });
          i.on("mouseleave", () => {
            this.stage.container().style.cursor = "default";
          });
        });
      } else {
        this.getLayerElements().forEach((i) => {
          i.off("mouseenter");
          i.off("mouseleave");
        });
      }
    },
    // 获取当前层级元素列表
    getLayerElements() {
      return this.elementLayer
        .getChildren()
        .filter((i) => i.getClassName() !== "Transformer");
    },
    // 获取当前层级元素属性列表
    getLayerElementsProperty() {
      return this.getLayerElements().map((element) => element.attrs);
    },
    // 回显元素
    echoElement(element) {
      const { _index, _level, _type, _shape, x, y, width, height } = element;
      this.createRect({ _index, _level, _type, x, y, width, height });
    },
    // $获取项目信息
    async getProjectData() {
      // 判断项目是否存在
      const exist = await getStoreExist(this.projectName);
      if (!exist.data) {
        this.$message.error("项目不存在");
        this.$router.push("/");
        return;
      }
      const keys = [
        "name",
        "pdf",
        "scale",
        "currentPage",
        "writecode",
        "readcode",
      ];
      const result = await Promise.all(keys.map((i) => this.getStoreValue(i)));
      console.log("获取项目信息", result);
      this.project.name = result[0].data;
      this.project.pdf = result[1].data;
      this.project.scale = result[2].data;
      this.project.writecode = result[4].data;
      this.project.readcode = result[5].data;
      // 加载PDF文档
      await this.loadingPDFDocuments();
      // gengx当前页
      this.project.currentPage = result[3].data;
    },
    // $加载PDF文档
    async loadingPDFDocuments() {
      const PDF = await loading({
        text: "PDF加载中...",
        fn: async () => await getPDFData(this.project.pdf.url),
      });
      const { numPages } = PDF;
      console.log("PDF信息", PDF);
      this.PDFObject = PDF;
      this.PDFMaxPageNumber = numPages;
      this.PDFPageList = Array.from(new Array(numPages).keys()).map(
        (i) => i + 1
      );
      this.background = document.getElementById("background");
      this.backgroundContext = this.background.getContext("2d");
    },
    // $PDF实例 页码 缩放比例
    togglePDFPage() {
      this.PDFObject.getPage(this.project.currentPage).then(async (res) => {
        await new Promise((resolve) => this.$nextTick(resolve));
        // 当前页视图对象
        const devicePixelRatio = 1;
        console.log("devicePixelRatio", devicePixelRatio);
        const viewport = res.getViewport({ scale: this.project.scale });
        console.log("viewport", viewport);
        const { width, height } = viewport;
        this.width = width * devicePixelRatio;
        this.height = height * devicePixelRatio;
        // 初始化stage
        this.initStage();
        const transform = [devicePixelRatio, 0, 0, devicePixelRatio, 0, 0];
        // 渲染内容
        res.render({
          canvasContext: this.backgroundContext,
          transform,
          viewport,
        });
      });
    },
    // 获取仓库值
    getStoreValue(key) {
      return getStoreValue(this.projectName, key);
    },
    // 更新仓库值
    putStoreValue(key, value) {
      return putStoreValue(this.projectName, key, value);
    },
    // $更新数据库中currentPage
    async putCurrentPage(page) {
      const result = await this.putStoreValue("currentPage", page);
      console.log("更新当前页码", result);
    },
    // $获取数据库中当前页的元素列表
    async getCurrentPageElements() {
      const { code, msg, data } = await loading({
        text: "获取当前页元素...",
        fn: async () => await this.getStoreValue(this.project.currentPage),
      });
      console.log(`第${this.project.currentPage}页元素`, data);
      if (!data) {
        this.currentPageElements = [];
        return;
      }
      if (code === 200) {
        // 回显矩形
        data.map((i) => this.echoElement(i));
        // 绘制
        this.elementLayer.draw();
        // 更新页面元素列表
        this.currentPageElements = this.currentElementsToList();
        return;
      }
    },
    // $更新数据库中当前页的元素列表
    async putCurrentPageElements() {
      const result = await this.putStoreValue(
        this.project.currentPage,
        this.getLayerElementsProperty()
      );
      console.log(`更新第${this.project.currentPage}页元素`, result);
      // 更新模板元素列表
      this.currentPageElements = this.currentElementsToList();
    },
    // $当前页元素转列表
    currentElementsToList() {
      return this.getLayerElements().map((i, index) => {
        const { _shape, _type } = i.attrs;
        const shape = ["矩形", "圆形"][_shape];
        const type = ["手写码", "点读码"][_type];
        const name = `${shape}${type}${index + 1}`;
        const image = require(`@/assets/icon/${["rect", "circle"][_shape]}_${["write", "read"][_type]
          }.svg`);
        return {
          name,
          image,
          element: i,
        };
      });
    },
  },
  mounted() {
    this.getProjectData();
  },
};
</script>